package com.uit.httt04.duongvanlam.facedetection.activity;

import static com.googlecode.javacv.cpp.opencv_core.*;
import static com.googlecode.javacv.cpp.opencv_imgproc.*;
import static com.googlecode.javacv.cpp.opencv_objdetect.*;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.googlecode.javacv.cpp.opencv_core.CvMat;
import com.googlecode.javacv.cpp.opencv_core.CvMemStorage;
import com.googlecode.javacv.cpp.opencv_core.CvRect;
import com.googlecode.javacv.cpp.opencv_core.CvSeq;
import com.googlecode.javacv.cpp.opencv_core.IplImage;
import com.googlecode.javacv.cpp.opencv_objdetect.CvHaarClassifierCascade;
import com.uit.httt04.duongvanlam.facedetection.R;
import com.uit.httt04.duongvanlam.facedetection.algorithm.Algorithm;
import com.uit.httt04.duongvanlam.facedetection.classes.ObjectAlgorithm;
import com.uit.httt04.duongvanlam.facedetection.extend.CameraPreview;
import com.uit.httt04.duongvanlam.facedetection.imageprocessing.ImageProcessing;
import com.uit.httt04.duongvanlam.facedetection.utils.ClassFile;
import com.uit.httt04.duongvanlam.facedetection.utils.ClassLogger;
import com.uit.httt04.duongvanlam.facedetection.utils.ClassToast;
import com.uit.httt04.duongvanlam.facedetection.utils.Configuration;
import com.uit.httt04.duongvanlam.facedetection.xml.AlgorithmXML;
import com.uit.httt04.duongvanlam.facedetection.xml.IDToNameXML;
import com.uit.httt04.duongvanlam.facedetection.xml.MatrixXML;
import com.uit.httt04.duongvanlam.facedetection.xml.ParserXML;
import com.uit.httt04.duongvanlam.facedetection.xml.ProjectedObjectXML;
import com.uit.httt04.duongvanlam.facedetection.xml.TrainingXML;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;

@SuppressLint(
{ "UseSparseArrays", "SimpleDateFormat" })
public class UnlockActivity extends Activity implements PreviewCallback
{
	private String TAG = "UnlockActivity";
	// <-- Camera
	private CameraPreview myCamera;
	private CvHaarClassifierCascade classifierCascade;
	private CvMemStorage memStorage;
	// Camera -->
	private int maxFacesToDetection;
	private boolean startCamera;
	private List<Algorithm> algorithms;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		try
		{
			//
			// <-- Initialize View
			//
			super.onCreate(savedInstanceState);
			//
			// Initialize View -->
			//

			this.algorithms = ((MyApplication) this.getApplication()).getAlgorithms();

			if (this.algorithms == null)
			{
				this.algorithms = xmlToObject(Configuration.TrainingFilePath);
				((MyApplication) this.getApplication()).setAlgorithms(algorithms);
			}

			if (this.algorithms != null)
			{
				//
				this.setContentView(R.layout.unlock_activity);
				this.myCamera = (CameraPreview) this.findViewById(R.id.unlock_activity_camerapreview);
				//
				this.startCamera = true;
				// Get max faces to detection
				this.maxFacesToDetection = Configuration.maxFacesToDetect;
				// Get file xml cascade
				File xmlFile = ClassFile.getCascadeXmlFile(this.getApplicationContext());
				if (xmlFile.exists() == true)
				{
					classifierCascade = new CvHaarClassifierCascade(cvLoad(xmlFile.getAbsolutePath()));
					if (classifierCascade != null)
					{
						memStorage = cvCreateMemStorage(0);
						if (this.memStorage == null)
						{
							throw new RuntimeException("Can't initialize");
						}
					}
				}
				// Initialize camera
				//this.myCamera.Load(this, this, true, Configuration.cameraHeight, Configuration.cameraWidth, Configuration.frontCamera);

				File folder = new File(Configuration.detectionFolderPath);
				if (folder.exists() == true)
				{
					File[] files = folder.listFiles();
					for (int i = 0; files != null && i < files.length; i++)
					{
						String inputFile = files[i].getAbsolutePath();
						try
						{
							ScanImage(inputFile, this.algorithms);
							System.gc();
						}
						catch (Exception e)
						{
							e.printStackTrace();
						}
					}
				} // End folder.exists()
			} // End this.algorithms != null
		} // End try
		catch (Exception e)
		{
			ClassToast.Show(this.getApplicationContext(), "Error", true);
			ClassLogger.Write(this.getClass().getName(), "onCreate", e.toString());
			e.printStackTrace();
		}

		// this.finish();
	}

	@Override
	public void onAttachedToWindow()
	{
		try
		{
			super.onAttachedToWindow();
			Window window = getWindow();
			window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
			window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
			window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
			window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
			window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
		}
		catch (Exception e)
		{
			ClassLogger.Write(this.getClass().getName(), "onAttachedToWindow", e.toString());
			e.printStackTrace();
		}
	}

	@Override
	protected void onRestart()
	{
		try
		{
			super.onRestart();
		}
		catch (Exception e)
		{
			ClassLogger.Write(this.getClass().getName(), "onRestart", e.toString());
			e.printStackTrace();
		}
	}

	@Override
	protected void onResume()
	{
		try
		{
			super.onResume();
		}
		catch (Exception e)
		{
			ClassLogger.Write(this.getClass().getName(), "onResume", e.toString());
			e.printStackTrace();
		}
	}

	@Override
	protected void onPause()
	{
		try
		{
			super.onPause();
		}
		catch (Exception e)
		{
			ClassLogger.Write(this.getClass().getName(), "onPause", e.toString());
			e.printStackTrace();
		}

	}

	@Override
	protected void onDestroy()
	{
		try
		{
			super.onDestroy();
			if (this.memStorage != null)
				cvReleaseMemStorage(this.memStorage);
			if (this.classifierCascade != null)
				cvReleaseHaarClassifierCascade(this.classifierCascade);
		}
		catch (Exception e)
		{
			ClassLogger.Write(this.getClass().getName(), "onDestroy", e.toString());
			e.printStackTrace();
		}
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event)
	{
		try
		{
			//if (keyCode == KeyEvent.KEYCODE_BACK) { return true; }
		}
		catch (Exception e)
		{
			ClassLogger.Write(this.getClass().getName(), "onKeyDown", e.toString());
			e.printStackTrace();
		}

		return super.onKeyDown(keyCode, event);
	}

	@Override
	public synchronized void onPreviewFrame(byte[] data, Camera camera)
	{
		Log.d(TAG, "onPreviewFrame");
		if (this.startCamera == true)
		{
			try
			{
				if (data.length > 0 && camera != null)
				{
					// get timestamp for set file's name
					long timeStamp = new Date().getTime();
					// camera's size
					Size cameraSize = camera.getParameters().getPreviewSize();
					int height = cameraSize.height;
					int width = cameraSize.width;
					// Convert YCrCr to RGB
					CvMat dataYCrCb = cvCreateMat(height * 3 / 2, width, CV_8UC1);
					dataYCrCb.getByteBuffer().put(data);
					CvMat dataRGB = ImageProcessing.YCrCbToRGB(dataYCrCb, height, width);

					// <-- Save file for testing
					//IplImage imageRGB = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
					//imageRGB = cvGetImage(dataRGB, imageRGB);
					//ImageProcessing.SaveImageFromIplImage(imageRGB, Configuration.detectionFolderPath + "/" + timeStamp + "_imageRGB" + Configuration.AllowedExt); // int[]
					//int[] dataRGB2 = new int[height * width];
					//ImageProcessing.YCrCbToRGB2(dataRGB2, data, width, height);
					//Bitmap imageRGB2 = Bitmap.createBitmap(dataRGB2, width, height, Config.ARGB_8888);
					//ImageProcessing.SaveImageFromBitmap(imageRGB2, Configuration.detectionFolderPath + "/" + timeStamp + "_imageRGB2" + Configuration.AllowedExt);

					// Detection faces
					Detection(dataRGB, height, width, Configuration.detectionFolderPath + "/" + timeStamp);
					// cvReleaseImage(imageRGB);
					cvReleaseMat(dataYCrCb);
					cvReleaseMat(dataRGB);
				}
			}
			catch (Exception e)
			{
				ClassLogger.Write(this.getClass().getName(), "onPreviewFrame", e.toString());
				e.printStackTrace();
			}
			finally
			{
				try
				{
					this.myCamera.getFrame();
				}
				catch (Exception e)
				{
					ClassLogger.Write(this.getClass().getName(), "onPreviewFrame", e.toString());
				}
			}
		}
	}

	private void Detection(CvMat ImageData, int height, int width, String OutputImagePath) throws Exception
	{
		try
		{
			if (ImageData != null && ImageData.empty() == false)
			{
				IplImage originalImage = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
				originalImage = cvGetImage(ImageData, originalImage);
				//
				IplImage grayImage = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
				cvCvtColor(originalImage, grayImage, CV_BGR2GRAY);
				//

				CvSeq faces = null;
				if (this.memStorage != null && this.classifierCascade != null)
				{
					faces = cvHaarDetectObjects(grayImage, this.classifierCascade, this.memStorage, 1.1, 0, CV_HAAR_FEATURE_MAX | CV_HAAR_SCALE_IMAGE | CV_HAAR_FIND_BIGGEST_OBJECT | CV_HAAR_DO_ROUGH_SEARCH | CV_HAAR_DO_CANNY_PRUNING, cvSize(Configuration.imageWidthMinSize, Configuration.imageHeightMinSize), cvSize(Configuration.imageWidthMaxSize, Configuration.imageHeightMaxSize));
				}

				String result = "";
				if (faces != null)
				{
					for (int i = 0; i < faces.total() && i < this.maxFacesToDetection; i++)
					{
						CvRect rect = new CvRect(cvGetSeqElem(faces, i));
						if (rect.width() >= Configuration.imageWidthMinSize && rect.width() <= Configuration.imageWidthMaxSize && rect.height() >= Configuration.imageHeightMinSize && rect.height() <= Configuration.imageHeightMaxSize)
						{
							//
							IplImage cropped = cvCreateImage(cvSize(rect.width(), rect.height()), originalImage.depth(), originalImage.nChannels());
							cvSetImageROI(originalImage, rect);
							cvCopy(originalImage, cropped);
							//
							//
							// <-- Save image
							//
							String outputImagePath = OutputImagePath + "." + i + Configuration.SavedExt;
							ImageProcessing.SaveImageFromIplImage(cropped, outputImagePath);
							// result = this.ScanImage(outputImagePath, this.algorithms);
							//
							// Save image -->
							//
							//
							cvResetImageROI(originalImage);
							if (result.equalsIgnoreCase("") == false)
							{
								break;
							}
						} // End minWidth<=rect.width()<=maxWidth && minHeigh<=rect.height()<=maxHeight
					} // End i < faces.total()

				} // End faces != null

				if (faces != null)
					cvClearSeq(faces);

				if (this.memStorage != null)
					cvClearMemStorage(this.memStorage);

				if (grayImage != null)
					cvReleaseImage(grayImage);
				if (originalImage != null)
					cvReleaseImage(originalImage);

				if (result.equalsIgnoreCase("") == false)
				{
					Toast.makeText(this.getApplicationContext(), "Hi, " + result, Toast.LENGTH_SHORT).show();
					this.StopCamera();
				}
			}
		}
		catch (Exception e)
		{
			throw e;
		}
	}

	private List<Algorithm> xmlToObject(String trainingFilePath) throws Exception
	{
		try
		{
			File xmlFileTraining = new File(trainingFilePath);
			if (xmlFileTraining.exists() == true)
			{
				// Input
				FileInputStream inputStream = new FileInputStream(xmlFileTraining);
				TrainingXML algorithmsXML = ParserXML.parse(inputStream);
				// Output
				List<Algorithm> algorithms = new ArrayList<Algorithm>();
				for (int i = 0; i < algorithmsXML.getListAlgorithm().size(); i++)
				{
					AlgorithmXML curAlgorithmXML = algorithmsXML.getListAlgorithm().get(i);

					Class<?> classObject = Class.forName(curAlgorithmXML.getAlgorithmName());
					Algorithm curAlgorithm = (Algorithm) classObject.newInstance();

					curAlgorithm.setHeight(curAlgorithmXML.getImageHeight());
					curAlgorithm.setWidth(curAlgorithmXML.getImageWidth());

					MatrixXML eigenvectorMatrixXML = curAlgorithmXML.getEigenvectorMatrix();
					CvMat eigenvectorMatrix = MatrixXML.MatrixXMLToCvMat(curAlgorithmXML.getMatrixDataSplitBy(), eigenvectorMatrixXML.getData(), eigenvectorMatrixXML.getHeight(), eigenvectorMatrixXML.getWidth());

					MatrixXML eigenvalueMatrixXML = curAlgorithmXML.getEigenvalueMatrix();
					CvMat eigenvalueMatrix = MatrixXML.MatrixXMLToCvMat(curAlgorithmXML.getMatrixDataSplitBy(), eigenvalueMatrixXML.getData(), eigenvalueMatrixXML.getHeight(), eigenvalueMatrixXML.getWidth());

					curAlgorithm.setProjectedEigenvect(eigenvectorMatrix);
					curAlgorithm.setEigenVal(eigenvalueMatrix);
					curAlgorithm.setNEigenvector(curAlgorithmXML.getEigenvectorMaxN());

					List<String> listAllowedExt = new ArrayList<String>();
					for (int j = 0; j < curAlgorithmXML.getAllowedExt().size(); j++)
					{
						listAllowedExt.add(curAlgorithmXML.getAllowedExt().get(j));
					}
					curAlgorithm.setAllowedExt(listAllowedExt);

					Map<Integer, String> autoIdToName = new HashMap<Integer, String>();
					for (int j = 0; j < curAlgorithmXML.getAutoID().size(); j++)
					{
						IDToNameXML idToNameXML = curAlgorithmXML.getAutoID().get(j);
						autoIdToName.put(idToNameXML.getID(), idToNameXML.getName());
					}
					curAlgorithm.setAutoIdToName(autoIdToName);

					List<ObjectAlgorithm> projectedListObject = new ArrayList<ObjectAlgorithm>();
					List<ProjectedObjectXML> projectedListObjectXML = curAlgorithmXML.getProjectedObjects();
					for (int j = 0; j < projectedListObjectXML.size(); j++)
					{
						ProjectedObjectXML objectXML = projectedListObjectXML.get(j);
						MatrixXML matrixProjectedObjectXML = objectXML.getMatrix();
						ObjectAlgorithm objectAlgorithm = new ObjectAlgorithm(MatrixXML.MatrixXMLToCvMat(curAlgorithmXML.getMatrixDataSplitBy(), matrixProjectedObjectXML.getData(), matrixProjectedObjectXML.getHeight(), matrixProjectedObjectXML.getWidth()), curAlgorithm.getIDByValue(objectXML.getPersonName()), objectXML.getImagePath());
						projectedListObject.add(objectAlgorithm);
					}

					curAlgorithm.setProjectedListObjects(projectedListObject);
					algorithms.add(curAlgorithm);
					// Free
					eigenvectorMatrixXML = null;
					eigenvalueMatrixXML = null;
					projectedListObjectXML = null;
					curAlgorithmXML = null;
				}
				algorithmsXML = null;
				inputStream.close();
				inputStream = null;
				System.gc();

				return algorithms;
			}
		}
		catch (Exception e)
		{
			ClassLogger.Write(this.getClass().getName(), "xmlToObject", e.toString());
			e.printStackTrace();
		}
		return null;
	}

	private void ScanImage(String ImagePath, List<Algorithm> algorithms) throws Exception
	{
		try
		{
			if (algorithms != null && algorithms.size() > 1)
			{

				int maxScore = 1000;

				SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Configuration.SimpleDateFormatPattern);

				Date starting = new Date();
				Log.i(TAG, "starting:" + simpleDateFormat.format(starting));

				// Get Default
				int amountOfFaces = algorithms.get(0).getAmountOfFaces();

				CvMat finalFacesResult = cvCreateMat(1, amountOfFaces, CV_64FC1);
				cvSetZero(finalFacesResult);

				for (int i = 0; i < algorithms.size(); i++)
				{
					CvMat temporaryFacesScore = algorithms.get(i).Test(ImagePath, 0.5, maxScore);
					if (temporaryFacesScore != null)
					{
						CvMat algorithmFacesResult = cvCreateMat(1, amountOfFaces, CV_64FC1);
						cvSetZero(algorithmFacesResult);

						for (int j = 0; j < temporaryFacesScore.cols(); j++)
						{
							int temporaryScore = (int) cvGetReal2D(temporaryFacesScore, 0, j);
							int curScore = (int) cvGetReal2D(finalFacesResult, 0, j);
							int newScore = temporaryScore + curScore;
							cvSetReal2D(finalFacesResult, 0, j, newScore + (j + Configuration.rounded) / Configuration.round2Decimal);
							cvSetReal2D(algorithmFacesResult, 0, j, temporaryScore + (j + Configuration.rounded) / Configuration.round2Decimal);
						}

						CvMat algorithmSortedFacesResult = cvCreateMat(1, amountOfFaces, CV_64FC1);
						cvSort(algorithmFacesResult, algorithmSortedFacesResult, null, CV_SORT_DESCENDING);
						cvReleaseMat(algorithmFacesResult);
						//
						double temporaryObject1 = cvGetReal2D(algorithmSortedFacesResult, 0, 0);
						int scoreObject1 = (int) temporaryObject1;
						int idObject1 = (int) ((temporaryObject1 - scoreObject1) * Configuration.round2Decimal);
						String personName1 = algorithms.get(i).getAutoIdToName().get(idObject1);

						double temporaryObject2 = cvGetReal2D(algorithmSortedFacesResult, 0, 1);
						int scoreObject2 = (int) temporaryObject2;
						int idObject2 = (int) ((temporaryObject2 - scoreObject2) * Configuration.round2Decimal);
						String personName2 = algorithms.get(i).getAutoIdToName().get(idObject2);

						writeResult(ImagePath, personName1 + "_" + scoreObject1, personName2 + "_" + scoreObject2, algorithms.get(i).getNEigenvector(), algorithms.get(i).getClass().getName());

						cvReleaseMat(algorithmSortedFacesResult);
					} // End temporaryResult != null
					cvReleaseMat(temporaryFacesScore);
				} // End this.algorithms.length

				CvMat sortedFinalFacesResult = cvCreateMat(1, amountOfFaces, CV_64FC1);
				cvSort(finalFacesResult, sortedFinalFacesResult, null, CV_SORT_DESCENDING);
				cvReleaseMat(finalFacesResult);
				//

				double temporaryObject1 = cvGetReal2D(sortedFinalFacesResult, 0, 0);
				int scoreObject1 = (int) temporaryObject1;
				int idObject1 = (int) ((temporaryObject1 - scoreObject1) * Configuration.round2Decimal);
				String personName1 = algorithms.get(0).getAutoIdToName().get(idObject1);

				double temporaryObject2 = cvGetReal2D(sortedFinalFacesResult, 0, 1);
				int scoreObject2 = (int) temporaryObject2;
				int idObject2 = (int) ((temporaryObject2 - scoreObject2) * Configuration.round2Decimal);
				String personName2 = algorithms.get(0).getAutoIdToName().get(idObject2);

				Date ending = new Date();
				Log.i(TAG, "ending:" + simpleDateFormat.format(ending));

				writeResult(ImagePath, personName1 + "_" + scoreObject1, personName2 + "_" + scoreObject2, algorithms.get(0).getNEigenvector(), "finalResult");

				// return finalPersonName;
			} // End algorithms.size() > 1
			else
			{
				throw new Exception("Amount of algorithm < 2");
			}
		} // End this.algorithms != null
		catch (Exception e)
		{
			ClassLogger.Write(this.getClass().getName(), "ScanImage", e.toString());
		}
		// return "";
	}

	@SuppressWarnings("unused")
	private void StartCamera() throws Exception
	{
		try
		{
			this.myCamera.startPreview();
			this.startCamera = true;
		}
		catch (Exception e)
		{
			throw e;
		}
	}

	private void StopCamera() throws Exception
	{
		try
		{
			this.myCamera.stopPreview();
			this.startCamera = false;
			this.finish();
		}
		catch (Exception e)
		{
			throw e;
		}
	}

	private void writeResult(String inputFile, String result1, String result2, int NEigenvector, String fileName)
	{
		BufferedWriter buf = null;
		try
		{
			File fileResult = new File(Configuration.detectionFolderPath, fileName);
			if (fileResult.exists() == false)
				fileResult.createNewFile();

			buf = new BufferedWriter(new FileWriter(fileResult, true));
			buf.append("------");
			buf.newLine();
			buf.write("File: " + inputFile);
			buf.newLine();
			buf.write("Result1: " + result1);
			buf.newLine();
			buf.write("Result2: " + result2);
			buf.newLine();
			buf.write("N: " + NEigenvector);
			buf.newLine();
			buf.append("------");
			buf.newLine();
			buf.newLine();
			buf.flush();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
			if (buf != null)
			{
				try
				{
					buf.close();
					buf = null;
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
			}
		}
	}
	//
}
